package vulnerability

import (
	"context"
	"fmt"
	"time"

	"github.com/jedib0t/go-pretty/v6/table"
	log "github.com/sirupsen/logrus"

	"github.com/slimtoolkit/slim/pkg/app"
	"github.com/slimtoolkit/slim/pkg/app/master/command"
	"github.com/slimtoolkit/slim/pkg/app/master/version"
	cmd "github.com/slimtoolkit/slim/pkg/command"
	//"github.com/slimtoolkit/slim/pkg/docker/dockerclient"
	"github.com/slimtoolkit/slim/pkg/report"
	//"github.com/slimtoolkit/slim/pkg/util/fsutil"
	"github.com/slimtoolkit/slim/pkg/util/jsonutil"
	//v "github.com/slimtoolkit/slim/pkg/version"
	"github.com/slimtoolkit/slim/pkg/vulnerability/epss"
	"github.com/slimtoolkit/slim/pkg/vulnerability/epss/client"
)

const appName = command.AppName

type ovars = app.OutVars

// OnEpssCommand implements the 'vulnerability epss' command
func OnEpssCommand(
	xc *app.ExecutionContext,
	gparams *command.GenericParams,
	cparams *EpssCommandParams) {
	cmdName := fullCmdName(EpssCmdName)
	logger := log.WithFields(log.Fields{
		"app": appName,
		"cmd": cmdName,
		"sub": EpssCmdName})

	viChan := version.CheckAsync(gparams.CheckVersion, gparams.InContainer, gparams.IsDSImage)

	cmdReport := report.NewVulnerabilityCommand(gparams.ReportLocation, gparams.InContainer)
	cmdReport.State = cmd.StateStarted
	cmdReport.Operation = cparams.Op

	xc.Out.State(cmd.StateStarted)
	xc.Out.Info("params",
		ovars{
			"cmd.params": jsonutil.ToString(cparams),
		})

	/*
		client, err := dockerclient.New(gparams.ClientConfig)
		if err == dockerclient.ErrNoDockerInfo {
			exitMsg := "missing Docker connection info"
			if gparams.InContainer && gparams.IsDSImage {
				exitMsg = "make sure to pass the Docker connect parameters to the docker-slim container"
			}

			xc.Out.Info("docker.connect.error",
				ovars{
					"message": exitMsg,
				})

			exitCode := command.ECTCommon | command.ECCNoDockerConnectInfo
			xc.Out.State("exited",
				ovars{
					"exit.code": exitCode,
					"version":   v.Current(),
					"location":  fsutil.ExeDir(),
				})
			xc.Exit(exitCode)
		}
		xc.FailOn(err)
	*/

	if gparams.Debug {
		version.Print(xc, cmdName, logger, nil /*client*/, false, gparams.InContainer, gparams.IsDSImage)
	}

	epssClient := client.New(client.Options{Debug: gparams.Debug})
	switch cparams.Op {
	case EpssOpList:
		callOptions := client.FilteredCallOptions{
			CallOptions: client.CallOptions{
				Date:     cparams.Date,
				PageSize: cparams.Limit,
				Offset:   cparams.Offset,
			},
			CveIDPattern:   cparams.FilterCveIDPattern,
			ScoreGt:        cparams.FilterScoreGt,
			ScoreLt:        cparams.FilterScoreLt,
			PercentileGt:   cparams.FilterPercentileGt,
			PercentileLt:   cparams.FilterPercentileLt,
			DaysSinceAdded: cparams.FilterDaysSinceAdded,
			OrderRecords:   cparams.FilterOrderRecords,
		}

		//note: not fetching all pages/records (should have a special flag for it)
		if cparams.WithHistory {
			scores, _, err := epssClient.ListScoresWithHistory(
				context.Background(),
				callOptions,
			)

			xc.FailOn(err)
			showScoresWithHistory(xc, scores)
		} else {
			scores, _, err := epssClient.ListScores(
				context.Background(),
				callOptions,
			)

			xc.FailOn(err)
			showScores(xc, scores)
		}
	default:
		callOptions := client.CallOptions{
			Date: cparams.Date,
		}

		if cparams.WithHistory {
			scores, _, err := epssClient.LookupScoresWithHistory(
				context.Background(),
				cparams.CVEList,
				callOptions)

			xc.FailOn(err)
			showScoresWithHistory(xc, scores)
		} else {
			scores, _, err := epssClient.LookupScores(
				context.Background(),
				cparams.CVEList,
				callOptions)

			xc.FailOn(err)
			showScores(xc, scores)
		}
	}

	xc.Out.State(cmd.StateCompleted)
	cmdReport.State = cmd.StateCompleted
	xc.Out.State(cmd.StateDone)

	vinfo := <-viChan
	version.PrintCheckVersion(xc, "", vinfo)

	cmdReport.State = cmd.StateDone
	if cmdReport.Save() {
		xc.Out.Info("report",
			ovars{
				"file": cmdReport.ReportLocation(),
			})
	}
}

func showScores(xc *app.ExecutionContext, scores []*epss.Score) {
	if xc.Out.Quiet {
		if xc.Out.OutputFormat == command.OutputFormatJSON {
			fmt.Printf("%s\n", jsonutil.ToPretty(scores))
			return
		}

		printScoresTable(scores)
		return
	}

	xc.Out.Info("epss.scores.summary", ovars{"count": len(scores)})
	for idx, score := range scores {
		fields := baseScoreFields(score)
		fields["index"] = idx
		xc.Out.Info("epss.score", fields)
	}
}

func printScoresTable(scores []*epss.Score) {
	tw := table.NewWriter()
	tw.AppendHeader(table.Row{"CVE ID", "Value", "Percentile", "Date"})

	for _, score := range scores {
		tw.AppendRow(table.Row{
			score.CVE,
			score.EPSS,
			score.Percentile,
			score.Date.Format(time.DateOnly),
		})
	}

	tw.SetStyle(table.StyleLight)
	tw.Style().Options.DrawBorder = false
	fmt.Printf("%s\n", tw.Render())
}

func printScoresWithHistoryTable(scores []*epss.ScoreWithHistory) {
	tw := table.NewWriter()
	tw.AppendHeader(table.Row{"CVE ID", "Value", "Percentile", "Date", "History"})

	for _, score := range scores {
		htw := table.NewWriter()
		htw.AppendHeader(table.Row{"Value", "Percentile", "Date"})
		for _, data := range score.History {
			htw.AppendRow(table.Row{
				data.EPSS,
				data.Percentile,
				data.Date.Format(time.DateOnly),
			})
		}

		htw.SetStyle(table.StyleLight)
		htw.Style().Options.DrawBorder = false
		tw.AppendRow(table.Row{
			score.CVE,
			score.EPSS,
			score.Percentile,
			score.Date.Format(time.DateOnly),
			htw.Render(),
		})
	}

	tw.SetStyle(table.StyleLight)
	tw.Style().Options.DrawBorder = false
	fmt.Printf("%s\n", tw.Render())
}

func showScoresWithHistory(xc *app.ExecutionContext, scores []*epss.ScoreWithHistory) {
	if xc.Out.Quiet {
		if xc.Out.OutputFormat == command.OutputFormatJSON {
			fmt.Printf("%s\n", jsonutil.ToPretty(scores))
			return
		}

		printScoresWithHistoryTable(scores)
		return
	}

	xc.Out.Info("epss.scores.summary", ovars{"count": len(scores)})
	for idx, score := range scores {
		fields := baseScoreFields(&score.Score)
		fields["index"] = idx
		for k, v := range scoreHistoryFields(score.History) {
			fields[k] = v
		}

		xc.Out.Info("epss.score", fields)
	}
}

func baseScoreFields(score *epss.Score) app.OutVars {
	return app.OutVars{
		"cve":        score.CVE,
		"value":      score.EPSS,
		"percentile": score.Percentile,
		"date":       score.Date.Format(time.DateOnly),
	}
}

func scoreHistoryFields(scoreHistory []epss.ScoreData) app.OutVars {
	fields := app.OutVars{}
	for idx, data := range scoreHistory {
		fields[fmt.Sprintf("history.%d", idx)] =
			fmt.Sprintf("%s/%f/%f",
				data.Date.Format(time.DateOnly),
				data.EPSS,
				data.Percentile)
	}

	return fields
}
